const _toString = Object.prototype.toString;
export function isFunction(val) {
  return typeof val === "function";
}

export function isObject(val) {
  return typeof val === "object" && val != null;
}
export function isPlainObject(obj) {
  return _toString.call(obj) === "[object Object]";
}

export function isReservedTag(tag) {
  const str = "html,body,base,head,link,meta,style,title," +
  "address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section," +
  "div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul," +
  "a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby," +
  "s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video," +
  "embed,object,param,source,canvas,script,noscript,del,ins," +
  "caption,col,colgroup,table,thead,tbody,td,th,tr," +
  "button,datalist,fieldset,form,input,label,legend,meter,optgroup,option," +
  "output,progress,select,textarea," +
  "details,dialog,menu,menuitem,summary," +
  "content,element,shadow,template,blockquote,iframe,tfoot"
  const arr = str.split(',')
  return arr.includes(tag)
}

function flushCallbacks() {
  callbacks.forEach((cb) => cb());
  waiting = false;
}

let waiting = false;
const callbacks = [];

function timer() {
  let timerFn = () => {};

  if (Promise) {
    timerFn = () => {
      Promise.resolve().then(flushCallbacks);
    };
  } else if (MutationObserver) {
    let textNode = document.createTextNode();
    textNode.textContent = 3;
    let observe = new MutationObserver(flushCallbacks);
    observe.observe(textNode, {
      characterData: true,
    });
    timerFn = () => {
      textNode.textContent = 3;
    };
  } else if (setImmediate) {
    timerFn = () => {
      setImmediate(flushCallbacks);
    };
  } else {
    timerFn = () => {
      setTimeout(flushCallbacks);
    };
  }
  timerFn();
}

export function nextTick(cb) {
  callbacks.push(cb);
  if (!waiting) {
    // setTimeout(flushCallbacks, 0);
    // vue2 中考虑了兼容性timer(flushCallbacks);
    timer()
    waiting = true;
  }
}

export const asset_types = ["component", "directive", "filter"];

export const liftcycle_hooks = [
  "beforeCreate",
  "created",
  "beforeMount",
  "monuted",
  "beforeUpdate",
  "updated",
  "beforeDestroy",
  "destroyed",
  "activated",
  "deactivated",
  "errorCaptured",
];

const strats = {};

liftcycle_hooks.forEach((hook) => {
  strats[hook] = mergeHook;
});

strats.data = function (parent, child, vm) {
  return mergeDataOrFn(parent, child, vm);
};

function mergeDataOrFn(parent, child, vm) {
  return function mergedInstanceDataFn() {
    const childData = isFunction(child) ? child.call(vm, vm) : child
    const parentData = isFunction(parent) ? parent.call(vm, vm) : parent
    if (childData) {
      return mergeData(childData, parentData);
    } else {
      return parentData;
    }
  };
}

function mergeData(to, from) {
  if (!from) return to;
  let keys = Object.keys(from);
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i];
    const toVal = to[key];
    const fromVal = from[key];
    if (!to.hasOwnProperty(key)) {
      to[key] = fromVal;
    } else if (
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal);
    }
  }
  return to;
}

function mergeHook(parent, child) {
  if (child) {
    if (parent) {
      return parent.concat(child);
    } else {
      if (Array.isArray(child)) {
        return child;
      } else {
        return [child];
      }
    }
  } else {
    return parent;
  }
}

strats.watch = function (parent, child, vm, key) {
  if (!parent) {
    return child;
  }
  if (!child) {
    return Object.create(parent || null);
  }
  const res = {};
  for (let key in parent) {
    res[key] = parent[key];
  }
  for (let key in child) {
    const parentVal = res[key];
    const childVal = child[key];
    if (!Array.isArray(parentVal)) {
      parentVal = [parentVal];
    }
    if (parentVal) {
      res[key] = parentVal.concat(childVal);
    } else {
      if (Array.isArray(childVal)) {
        res[key] = childVal;
      } else {
        res[key] = [childVal];
      }
    }
  }
  return res;
};

strats.props =
  strats.methods =
  strats.inject =
  strats.computed =
    function (parent, child, vm, key) {
      if (!parent) return child;

      const res = {};

      for (let key in parent) {
        res[key] = parent[key];
      }
      if (child) {
        for (let key in child) {
          res[key] = child[key];
        }
      }
      return res;
    };

function mergeAsset(parent, child) {
  const res = Object.create(parent || null);
  if (child) {
    for (let key in child) {
      res[key] = child[key];
    }
  }
  return res;
}

function defaultStrat(parent, child) {
  if (!child) {
    return parent;
  } else {
    return child;
  }
}

strats.components = function (parentVal, childVal) {
  // 根据父对象构造一个新的对象。 options.__proto__
  let options = Object.create(parentVal);
  if (childVal) {
    for (let key in childVal) {
      options[key] = childVal[key];
    }
  }
  return options
};

export function mergeOptions(parent, child, vm) {
  if (child.mixins) {
    for (let i = 0; i < child.mixins.length; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
  const options = {};
  // 1遍历合并parent中的key
  for (let key in parent) {
    mergeField(key);
  }
  // 2.不上parent中没有的key
  for (let key in child) {
    if (!parent.hasOwnProperty(key)) {
      mergeField(key);
    }
  }

  function mergeField(key) {
    const strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options;
}

// function mergeOptions(parent, child) {
//   const options = {};

//   for (let key in parent) {
//     mergeField(key);
//   }

//   for (let key in child) {
//     if (parent.hasOwnProperty(key)) {
//       continue;
//     }
//     mergeField(key);
//   }

//   function mergeField(key) {
//     let parentVal = parent[key];
//     let childVal = child[key];
//     if (isObject(parentVal) && isObject(childVal)) {
//       options[key] = {
//         ...parentVal,
//         ...childVal,
//       };
//     } else {
//       options[key] = child[key];
//     }
//   }
//   return options;
// }
